package com.moons.uvcdemo;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Application;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Environment;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.folderselector.FileChooserDialog;
import com.moons.uvcdemo.config.DemoConfig;
import com.moons.uvcdemo.model.UserFace;
import com.moons.uvcdemo.util.ARUtil;
import com.moons.uvcdemo.util.FileUtil;
import com.moons.uvcdemo.utils.ApplicationMe;
import com.tbruyelle.rxpermissions2.RxPermissions;
import com.xqd.sdk.libaicamera.AICamera;
import com.xqd.sdk.libaicamera.AICameraManager;
import com.xqd.sdk.libaicamera.face.FaceAttribute;
import com.xqd.sdk.libaicamera.face.FaceObject;
import com.xqd.sdk.libaicamera.face.FaceObjectParser;
import com.xqd.sdk.libaicamera.face.FaceRegister;
import com.xqd.sdk.libaicamera.utils.Utils;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import io.reactivex.functions.Consumer;

/**
 * 此Demo主要演示从图片中获取人脸特征值，并保存于SDcard，供Demo5<a href="https://gitee.com/my10moons/uvc_face_recognition_demo">简单人脸识别</a>使用。
 *
 * @author 10moons
 */
public class RegisterActivity extends AppCompatActivity implements FileChooserDialog.FileCallback {
    private static final String TAG = "MainActivity";

    private SurfaceView mSurfaceView;
    private SurfaceView mFaceSurfaceView;
    private TextView mVersionTextView;

    private static Paint paint;
    private static int fontSize = 20;
    private static int rectSize = 4;

    private ArrayBlockingQueue<byte[]> mFrameDataQueue;
    private ArrayBlockingQueue<byte[]> mFaceDataQueue;
    private ArrayBlockingQueue<Bitmap> mBitmapFrameQueue;

    private DecodeThread mDecodeThread;
    private DrawFaceThread mDrawFaceThread;
    private DrawFrameThread mDrawFrameThread;

    private AICamera mAICamera;
    private long mLastFrameTime;

    FileChooserDialog mFileChooserDialog;
    private WeakReference<RegisterActivity> mMainActivityWeakReference;

    private final Object mFrameCountLock = new Object();

    private boolean isPreviewAvailable;
    private boolean isOverlayAvailable;

    private AICameraManager mAICameraManager;
    private int mNoFaceFrameCount = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        mMainActivityWeakReference = new WeakReference<>(this);

        initPermission();
        initViews();
        initDeviceListener();
        initQueues();
        initThread();
        mLastFrameTime = System.currentTimeMillis();
    }

    /**
     * 初始化设备监听
     */
    private void initDeviceListener() {

        mAICameraManager = AICameraManager.instance();

        mAICameraManager.setDeviceEventListener(new AICameraManager.DeviceEventListener() {
            @Override
            public void onDeviceAdded(AICamera aiCamera) {
                Log.d(TAG, "onDeviceAdded: " + aiCamera.getPath());
                if (aiCamera.open()) {

                    List<Size> previewSizes = aiCamera.getSupportPreviewSizes();
                    if (previewSizes.size() > 0) {
                        for (Size sz : previewSizes) {
                            Log.d(TAG, String.format("%d*%d", sz.getWidth(), sz.getHeight()));
                            if (is720P(sz)) {
                                Log.d(TAG, "onDeviceAdded: set 720p");
                                aiCamera.setPreviewSize(sz);
                            }
                        }
                    }
                    aiCamera.setCaptureCallback(mCaptureCallback);
                    aiCamera.startCapture();
                    mAICamera = aiCamera;
                }
            }

            @Override
            public void onDeviceRemoved(AICamera aiCamera) {
                aiCamera.setCaptureCallback(null);
                aiCamera.stopCapture();
                aiCamera.close();
                mAICamera = null;
            }
        });
        mAICameraManager.start();

    }

    private boolean is720P(Size size) {
        return ((size.getWidth() == 1280) && (size.getHeight() == 720) || ((size.getWidth() == 720) && (size.getHeight() == 1280)));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mAICamera != null) {
            mAICamera.stopCapture();
            mAICamera.release();
        }

    }

    @SuppressLint("CheckResult")
    private void initPermission() {
        new RxPermissions(this).request(Manifest.permission.READ_EXTERNAL_STORAGE)
                .subscribe(new Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean aBoolean) throws Exception {
                        if (!aBoolean) {
                            Toast.makeText(RegisterActivity.this, "请授予读取权限，否则本demo不能正常演示。", Toast.LENGTH_SHORT).show();
                        }

                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        throwable.printStackTrace();

                    }
                });
    }


    private void initViews() {
        mVersionTextView = findViewById(R.id.version);
        mVersionTextView.setText(AICamera.getSDKVersion());
        mSurfaceView = findViewById(R.id.surfaceView_camera_preview);

        mSurfaceView.setZOrderOnTop(false);
        mFaceSurfaceView = findViewById(R.id.surfaceView_face_view);
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                isPreviewAvailable = true;
                holder.setFormat(PixelFormat.RGBA_8888);
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isPreviewAvailable = false;
                Log.d(TAG, "mSurfaceView    surfaceDestroyed: ");
            }
        });
        mFaceSurfaceView.setZOrderOnTop(true);
        mFaceSurfaceView.setZOrderMediaOverlay(true); //把该sufaceview置于其余surfaceview的最上方
        mFaceSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                holder.setFormat(PixelFormat.TRANSLUCENT);

                isOverlayAvailable = true;

            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isOverlayAvailable = false;
                Log.d(TAG, "mFaceSurfaceView    surfaceDestroyed: ");
            }
        });


        Button button = findViewById(R.id.bt_regist_bt_bitmap);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mMainActivityWeakReference.get() != null) {
                    if (mFileChooserDialog == null) {
                        mFileChooserDialog = new FileChooserDialog.Builder(getApplicationContext())
                                .initialPath(Environment.getExternalStorageDirectory().getPath())  // changes initial path, defaults to external storage directory
                                .mimeType("image/*") // Optional MIME type filter
                                .extensionsFilter(".png", ".jpg", ".jpeg") // Optional extension filter, will override mimeType()
                                .tag("optional-identifier")
//                                .goUpLabel("向上") // custom go up label, default label is "..."
                                .build(); // an AppCompatActivity which implements FileCallback
                    }
                    mFileChooserDialog.show(mMainActivityWeakReference.get());
                }
            }
        });
    }

    private void initQueues() {
        mFrameDataQueue = new ArrayBlockingQueue<>(2, true);
        mFaceDataQueue = new ArrayBlockingQueue<>(2);
        mBitmapFrameQueue = new ArrayBlockingQueue<>(2);
        mFrameDataQueue.clear();
        mFaceDataQueue.clear();
        mBitmapFrameQueue.clear();
    }

    private void initThread() {
        mDecodeThread = new DecodeThread();
        mDrawFaceThread = new DrawFaceThread();
        mDrawFrameThread = new DrawFrameThread();
        mDecodeThread.start();
        mDrawFaceThread.start();
        mDrawFrameThread.start();
    }


    private void DrawFrame(final Bitmap bitmap) {
//        Log.d(TAG, "DrawFrame start");
        Rect dest = new Rect(0, 0, mSurfaceView.getWidth(), mSurfaceView.getHeight());
        Paint paint = new Paint();
        //paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setFilterBitmap(true);
        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        Canvas canvas = mSurfaceView.getHolder().lockCanvas();
        if (canvas == null) {
            return;
        }
        canvas.drawBitmap(bitmap, src, dest, paint);
        mSurfaceView.getHolder().unlockCanvasAndPost(canvas);
        if (!bitmap.isRecycled()) {
            bitmap.recycle();
        }
//        Log.d(TAG, "DrawFrame end");
    }

    /**
     * 画人脸框
     *
     * @param faceObjectList 人脸数据信息，可能有多个
     */
    private void DrawFaces(final List<FaceObject> faceObjectList, Size size) {

        float widthScale = (float) mFaceSurfaceView.getWidth() / size.getWidth();
        float heightScale = (float) mFaceSurfaceView.getHeight() / size.getHeight();
        Canvas canvas = mFaceSurfaceView.getHolder().lockCanvas();
        if (canvas == null) {
            return;
        }
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        for (FaceObject faceObject : faceObjectList) {
            Paint paint = new Paint();
            paint.setStrokeWidth(5);
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(Color.WHITE);
            paint.setAntiAlias(true);

            RectF rect = new RectF();
            rect.left = faceObject.rect().left;
            rect.right = faceObject.rect().right;
            rect.top = faceObject.rect().top;
            rect.bottom = faceObject.rect().bottom;

            float originFaceWidth = rect.width();
            float originFaceHeight = rect.height();

            float finalWidth = originFaceWidth * widthScale;
            float finalHeight = originFaceHeight * heightScale;
            float orginX = rect.left + originFaceWidth / 2;
            float orginY = rect.top + originFaceHeight / 2;
            float x = orginX * widthScale;
            float y = orginY * heightScale;

            rect.left = x - finalWidth / 2;
            rect.right = x + finalWidth / 2;
            rect.top = y - finalHeight / 2;
            rect.bottom = y + finalHeight / 2;

            canvas.drawRect(rect, paint);

            drawFaceInfo(faceObject, rect, canvas);
        }
        mFaceSurfaceView.getHolder().unlockCanvasAndPost(canvas);
    }

    private static void drawFaceInfo(FaceObject faceSegment, RectF rect, Canvas canvas) {
        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.WHITE);
        paint.setTextSize(fontSize);
        paint.setAntiAlias(true);
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();  //字体高度
        //每一行文字的高度
        float fHeight = fontMetrics.bottom - fontMetrics.top;
        //信息卡矩形图片
        RectF rectB = new RectF();
        rectB.left = rect.left - fHeight * 10;
        rectB.top = rect.top;
        rectB.right = rect.left;
        rectB.bottom = rect.top + fHeight * 6;
        Bitmap bitmap = BitmapFactory.decodeResource(ApplicationMe.getAppContext().getResources(), R.drawable.male);
        canvas.drawBitmap(bitmap, null, rectB, paint);
        //写人脸信息
        // blur、YPR角度信息

        int mYaw = (int)faceSegment.angle().mYaw;
        int mPitch = (int)faceSegment.angle().mPitch;
        int mRoll = (int)faceSegment.angle().mRoll;
        String s_gender = "man";
        FaceAttribute.Gender gender = faceSegment.attribute().gender();
        if(gender == (FaceAttribute.Gender.FEMALE))
        {
            s_gender = "women";
        }

        String s_pose = String.format("blur:%.3f y:%d p:%d r:%d",
                faceSegment.attribute().mBlur,mYaw, mPitch, mRoll);
        canvas.drawText(s_pose, rectB.left + fHeight, rectB.top + fHeight, paint);

        if (!(faceSegment == null)) {
            String s_score = String.format("score:%.3f", faceSegment.attribute().mScore);
            canvas.drawText(s_score, rectB.left + fHeight, rectB.top + fHeight * 2, paint);

            String s_attr1 = String.format("gender:%s age:%d skincolor:%d",
                    s_gender, faceSegment.attribute().mAge, faceSegment.attribute().mSkinColor);
            canvas.drawText(s_attr1, rectB.left + fHeight, rectB.top + fHeight * 3, paint);

            String s_attr2 = String.format("glass:%b mouth:%b",
                    faceSegment.attribute().mHasGlasses, faceSegment.attribute().mIsMouthOpened);
            canvas.drawText(s_attr2, rectB.left + fHeight, rectB.top + fHeight * 4, paint);

            String s_attr3 = String.format("eye:%b smile:%b",
                    faceSegment.attribute().mIsEyeClosed, faceSegment.attribute().isSmile());
            canvas.drawText(s_attr2, rectB.left + fHeight, rectB.top + fHeight * 5, paint);
        }

    }

    @Override
    /**
     * 文件选择回调
     */
    public void onFileSelection(@NonNull FileChooserDialog fileChooserDialog, @NonNull final File file) {
        Log.d(TAG, "onFileSelection: " + file.getAbsolutePath());

        if (mAICamera != null) {


            new Thread(new Runnable() {
                @Override
                public void run() {

                    FaceRegister faceRegister = mAICamera.getFaceRegister(getApplicationContext());
                    if (faceRegister != null) {

                        byte[] buffer = FileUtil.getInstance().loadPictureFile(file);
                        if (buffer != null) {
                            List<FaceObject> faceObjectList = faceRegister.registerFromJpeg(buffer);

                            if (faceObjectList != null && !faceObjectList.isEmpty()) {

                                for (FaceObject faceObject : faceObjectList) {

                                    if (faceObject.hasFeatures()) {

                                        final StringBuilder stringBuilder = new StringBuilder();
                                        final float[] features = Arrays.copyOf(faceObject.mFeatures, faceObject.mFeatures.length);

                                        //为了演示，只取前1/5的特征值
                                        for (int i = 0; i < faceObject.mFeatures.length / 5; i++) {
                                            stringBuilder.append(faceObject.mFeatures[i]);
                                        }
                                        stringBuilder.append("......");
                                        //读取输入的图片
                                        Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());

                                        if (bitmap != null && faceObject.rect().left + faceObject.rect().width() < bitmap.getWidth() && faceObject.rect().top + faceObject.rect().height() < bitmap.getHeight()) {

                                            //根据返回的faceobject 抠出人脸图片
                                            final Bitmap facePicture = Bitmap.createBitmap(bitmap, faceObject.rect().left, faceObject.rect().top, faceObject.rect().width(), faceObject.rect().height());

                                            if (mMainActivityWeakReference.get() != null) {


                                                runOnUiThread(new Runnable() {
                                                    @Override
                                                    public void run() {

                                                        new MaterialDialog.Builder(mMainActivityWeakReference.get())
                                                                .title("识别成功，特征值为：")
                                                                .positiveText("保存数据到SDCard")
                                                                .negativeText("不保存")
                                                                .icon(new BitmapDrawable(facePicture))
                                                                .content(stringBuilder.toString())
                                                                .onPositive(new MaterialDialog.SingleButtonCallback() {
                                                                    @Override
                                                                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                                                                        new MaterialDialog.Builder(mMainActivityWeakReference.get())
                                                                                .title("请输入姓名:")
                                                                                .icon(new BitmapDrawable(facePicture))
                                                                                .inputType(InputType.TYPE_CLASS_TEXT)
                                                                                .input("张三", "", new MaterialDialog.InputCallback() {
                                                                                    @Override
                                                                                    public void onInput(MaterialDialog dialog, CharSequence input) {
                                                                                        // Do something
                                                                                        if (!TextUtils.isEmpty(input)) {

                                                                                            UserFace userFace = new UserFace();
                                                                                            userFace.setName(input.toString());
                                                                                            userFace.setFeature(ARUtil.floatArr2byteArr(features));
                                                                                            FileUtil.getInstance().saveUserFaceToSDCard(userFace);
//                                                        ApplicationMe.getInstance().getDaoSession().getUserFaceDao().insertOrReplace(userFace);
                                                                                            Toast.makeText(RegisterActivity.this, "特征值已保存在:" + DemoConfig.FeaturesPath + "  路径下。", Toast.LENGTH_LONG).show();
                                                                                        }


                                                                                    }
                                                                                }).show();
                                                                    }
                                                                })
                                                                .show();

                                                    }
                                                });


                                            }
                                        }

                                        // TODO: 2020/4/8  仅演示对识别到的第一个人脸处理
                                        break;

                                    }


                                }


                            }


                        }


                    }
                }
            }).start();
        } else {
            Toast.makeText(this, "摄像头未初始化", Toast.LENGTH_SHORT).show();
        }


    }

    @Override
    public void onFileChooserDismissed(@NonNull FileChooserDialog fileChooserDialog) {
        //不选择任何文件，不处理
    }

    private class DrawFrameThread extends Thread {

        private boolean mRunning;

        public DrawFrameThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    Bitmap bitmap = mBitmapFrameQueue.poll(2, TimeUnit.MILLISECONDS);
                    if (!isPreviewAvailable) {
                        if (bitmap != null) {
                            bitmap.recycle();
                            bitmap = null;
                            continue;
                        }
                    }
                    if (bitmap != null) {
                        DrawFrame(bitmap);
                    } else {
                        //Log.d(TAG, "poll data timeout");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private class DrawFaceThread extends Thread {

        private boolean mRunning;

        public DrawFaceThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    byte[] data = mFaceDataQueue.poll(1, TimeUnit.MILLISECONDS);
                    if (data != null && mAICamera != null) {

                        Size size = Utils.getSizeFromFaceData(data);

                        FaceObjectParser faceObjectParser = mAICamera.getFaceObjectParser();
                        if (faceObjectParser != null) {

                            List<FaceObject> faceObjectList = faceObjectParser.getFaceObjectList(data, 0);

                            if (faceObjectList != null && !faceObjectList.isEmpty()) {

                                synchronized (mFrameCountLock) {
                                    mNoFaceFrameCount = 0;
                                    DrawFaces(faceObjectList, size);

                                }
                            }
                        }

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }


    private class DecodeThread extends Thread {

        private boolean mRunning;

        public DecodeThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while (mRunning) {
                try {
                    byte[] frame = mFrameDataQueue.poll(2, TimeUnit.MILLISECONDS);
                    if (frame != null) {
//                        Log.d(TAG, "decode start");
                        Bitmap bitmap = BitmapFactory.decodeByteArray(frame, 0, frame.length);
                        if (bitmap != null) {
//                            Log.d(TAG, "decode end");
                            if (!mBitmapFrameQueue.offer(bitmap)) {
//                                Log.d(TAG, "could not offer to mBitmapFrameQueue");
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    AICamera.CaptureCallback mCaptureCallback = new AICamera.CaptureCallback() {

        @Override
        public void onFrameData(byte[] data, AICamera camera) {

            if (!isPreviewAvailable) {
                return;
            }
//            Log.d(TAG, "onJpegFrame");
            long currentTime = System.currentTimeMillis();
            if ((currentTime - mLastFrameTime) < 20) {
//                Log.d(TAG, "ignore one frame");
            }
            synchronized (mFrameCountLock) {
                mNoFaceFrameCount++;
                if (mNoFaceFrameCount >= 10) {
                    clearOverlay(mFaceSurfaceView);
                    mNoFaceFrameCount = 0;
                }
            }
            if (!mFrameDataQueue.offer(data)) {
//                Log.d(TAG, "mFrameQueue failed: size ==> " + mFrameDataQueue.size() + " ,capacity ==> " + mFrameDataQueue.remainingCapacity() + "");
                mFrameDataQueue.clear();
                mFrameDataQueue.offer(data);
            }
        }

        @Override
        public void onFaceData(byte[] data, AICamera camera) {
            if (!isPreviewAvailable) {
                return;
            }
//            Log.d(TAG, "onDataFrame");


            if (!mFaceDataQueue.offer(data)) {
//                Log.d(TAG, "failed to offer a data queue, clear");
                mFaceDataQueue.clear();
                mFaceDataQueue.offer(data);
            }
        }
    };


    public static void clearOverlay(SurfaceView outputView) {
        Canvas canvas = null;
        try {
            canvas = outputView.getHolder().lockCanvas();
            if (canvas == null) {
                return;
            }
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outputView.getHolder().unlockCanvasAndPost(canvas);
            } catch (Exception e) {
                Log.e("SurfaceDraw", "can not unlock canvas");
            }
        }
    }
}
